1 /* SDLMain.m - main entry point for our Cocoa-ized SDL app 2 Initial Version: Darrell Walisser <dwaliss1 (at) purdue.edu> 3 Non-NIB-Code & other changes: Max Horn <max (at) quendi.de> 4 5 Feel free to customize this file to suit your needs 6 */ 7 8 #include "SDL.h" 9 #include "SDLMain.h" 10 #include <sys/param.h> /* for MAXPATHLEN */ 11 #include <unistd.h> 12 13 /* For some reaon, Apple removed setAppleMenu from the headers in 10.4, 14 but the method still is there and works. To avoid warnings, we declare 15 it ourselves here. */ 16 @interface NSApplication(SDL_Missing_Methods) 17 - (void)setAppleMenu:(NSMenu *)menu; 18 @end 19 20 /* Use this flag to determine whether we use SDLMain.nib or not */ 21 #define SDL_USE_NIB_FILE 0 22 23 /* Use this flag to determine whether we use CPS (docking) or not */ 24 #define SDL_USE_CPS 1 25 #ifdef SDL_USE_CPS 26 /* Portions of CPS.h */ 27 typedef struct CPSProcessSerNum 28 { 29 UInt32 lo; 30 UInt32 hi; 31 } CPSProcessSerNum; 32 33 extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn); 34 extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); 35 extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); 36 37 #endif /* SDL_USE_CPS */ 38 39 static int gArgc; 40 static char **gArgv; 41 static BOOL gFinderLaunch; 42 static BOOL gCalledAppMainline = FALSE; 43 44 static NSString *getApplicationName(void) 45 { 46 const NSDictionary *dict; 47 NSString *appName = 0; 48 49 /* Determine the application name */ 50 dict = (const NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle()); 51 if (dict) 52 appName = [dict objectForKey: @"CFBundleName"]; 53 54 if (![appName length]) 55 appName = [[NSProcessInfo processInfo] processName]; 56 57 return appName; 58 } 59 60 #if SDL_USE_NIB_FILE 61 /* A helper category for NSString */ 62 @interface NSString (ReplaceSubString) 63 - (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString; 64 @end 65 #endif 66 67 @interface SDLApplication : NSApplication 68 @end 69 70 @implementation SDLApplication 71 /* Invoked from the Quit menu item */ 72 - (void)terminate:(id)sender 73 { 74 /* Post a SDL_QUIT event */ 75 SDL_Event event; 76 event.type = SDL_QUIT; 77 SDL_PushEvent(&event); 78 } 79 @end 80 81 /* The main class of the application, the application's delegate */ 82 @implementation SDLMain 83 84 /* Set the working directory to the .app's parent directory */ 85 - (void) setupWorkingDirectory:(BOOL)shouldChdir 86 { 87 if (shouldChdir) 88 { 89 char parentdir[MAXPATHLEN]; 90 CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); 91 CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url); 92 if (CFURLGetFileSystemRepresentation(url2, 1, (UInt8 *)parentdir, MAXPATHLEN)) { 93 chdir(parentdir); /* chdir to the binary app's parent */ 94 } 95 CFRelease(url); 96 CFRelease(url2); 97 } 98 } 99 100 #if SDL_USE_NIB_FILE 101 102 /* Fix menu to contain the real app name instead of "SDL App" */ 103 - (void)fixMenu:(NSMenu *)aMenu withAppName:(NSString *)appName 104 { 105 NSRange aRange; 106 NSEnumerator *enumerator; 107 NSMenuItem *menuItem; 108 109 aRange = [[aMenu title] rangeOfString:@"SDL App"]; 110 if (aRange.length != 0) 111 [aMenu setTitle: [[aMenu title] stringByReplacingRange:aRange with:appName]]; 112 113 enumerator = [[aMenu itemArray] objectEnumerator]; 114 while ((menuItem = [enumerator nextObject])) 115 { 116 aRange = [[menuItem title] rangeOfString:@"SDL App"]; 117 if (aRange.length != 0) 118 [menuItem setTitle: [[menuItem title] stringByReplacingRange:aRange with:appName]]; 119 if ([menuItem hasSubmenu]) 120 [self fixMenu:[menuItem submenu] withAppName:appName]; 121 } 122 [ aMenu sizeToFit ]; 123 } 124 125 #else 126 127 static void setApplicationMenu(void) 128 { 129 /* warning: this code is very odd */ 130 NSMenu *appleMenu; 131 NSMenuItem *menuItem; 132 NSString *title; 133 NSString *appName; 134 135 appName = getApplicationName(); 136 appleMenu = [[NSMenu alloc] initWithTitle:@""]; 137 138 /* Add menu items */ 139 title = [@"About " stringByAppendingString:appName]; 140 [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; 141 142 [appleMenu addItem:[NSMenuItem separatorItem]]; 143 144 title = [@"Hide " stringByAppendingString:appName]; 145 [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; 146 147 menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; 148 [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; 149 150 [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; 151 152 [appleMenu addItem:[NSMenuItem separatorItem]]; 153 154 title = [@"Quit " stringByAppendingString:appName]; 155 [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; 156 157 158 /* Put menu into the menubar */ 159 menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; 160 [menuItem setSubmenu:appleMenu]; 161 [[NSApp mainMenu] addItem:menuItem]; 162 163 /* Tell the application object that this is now the application menu */ 164 [NSApp setAppleMenu:appleMenu]; 165 166 /* Finally give up our references to the objects */ 167 [appleMenu release]; 168 [menuItem release]; 169 } 170 171 /* Create a window menu */ 172 static void setupWindowMenu(void) 173 { 174 NSMenu *windowMenu; 175 NSMenuItem *windowMenuItem; 176 NSMenuItem *menuItem; 177 178 windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; 179 180 /* "Minimize" item */ 181 menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; 182 [windowMenu addItem:menuItem]; 183 [menuItem release]; 184 185 /* Put menu into the menubar */ 186 windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; 187 [windowMenuItem setSubmenu:windowMenu]; 188 [[NSApp mainMenu] addItem:windowMenuItem]; 189 190 /* Tell the application object that this is now the window menu */ 191 [NSApp setWindowsMenu:windowMenu]; 192 193 /* Finally give up our references to the objects */ 194 [windowMenu release]; 195 [windowMenuItem release]; 196 } 197 198 /* Replacement for NSApplicationMain */ 199 static void CustomApplicationMain (int argc, char **argv) 200 { 201 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 202 SDLMain *sdlMain; 203 204 /* Ensure the application object is initialised */ 205 [SDLApplication sharedApplication]; 206 207 #ifdef SDL_USE_CPS 208 { 209 CPSProcessSerNum PSN; 210 /* Tell the dock about us */ 211 if (!CPSGetCurrentProcess(&PSN)) 212 if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103)) 213 if (!CPSSetFrontProcess(&PSN)) 214 [SDLApplication sharedApplication]; 215 } 216 #endif /* SDL_USE_CPS */ 217 218 /* Set up the menubar */ 219 [NSApp setMainMenu:[[NSMenu alloc] init]]; 220 setApplicationMenu(); 221 setupWindowMenu(); 222 223 /* Create SDLMain and make it the app delegate */ 224 sdlMain = [[SDLMain alloc] init]; 225 [NSApp setDelegate:sdlMain]; 226 227 /* Start the main event loop */ 228 [NSApp run]; 229 230 [sdlMain release]; 231 [pool release]; 232 } 233 234 #endif 235 236 237 /* 238 * Catch document open requests...this lets us notice files when the app 239 * was launched by double-clicking a document, or when a document was 240 * dragged/dropped on the app's icon. You need to have a 241 * CFBundleDocumentsType section in your Info.plist to get this message, 242 * apparently. 243 * 244 * Files are added to gArgv, so to the app, they'll look like command line 245 * arguments. Previously, apps launched from the finder had nothing but 246 * an argv[0]. 247 * 248 * This message may be received multiple times to open several docs on launch. 249 * 250 * This message is ignored once the app's mainline has been called. 251 */ 252 - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename 253 { 254 const char *temparg; 255 size_t arglen; 256 char *arg; 257 char **newargv; 258 259 if (!gFinderLaunch) /* MacOS is passing command line args. */ 260 return FALSE; 261 262 if (gCalledAppMainline) /* app has started, ignore this document. */ 263 return FALSE; 264 265 temparg = [filename UTF8String]; 266 arglen = SDL_strlen(temparg) + 1; 267 arg = (char *) SDL_malloc(arglen); 268 if (arg == NULL) 269 return FALSE; 270 271 newargv = (char **) realloc(gArgv, sizeof (char *) * (gArgc + 2)); 272 if (newargv == NULL) 273 { 274 SDL_free(arg); 275 return FALSE; 276 } 277 gArgv = newargv; 278 279 SDL_strlcpy(arg, temparg, arglen); 280 gArgv[gArgc++] = arg; 281 gArgv[gArgc] = NULL; 282 return TRUE; 283 } 284 285 286 /* Called when the internal event loop has just started running */ 287 - (void) applicationDidFinishLaunching: (NSNotification *) note 288 { 289 int status; 290 291 /* Set the working directory to the .app's parent directory */ 292 [self setupWorkingDirectory:gFinderLaunch]; 293 294 #if SDL_USE_NIB_FILE 295 /* Set the main menu to contain the real app name instead of "SDL App" */ 296 [self fixMenu:[NSApp mainMenu] withAppName:getApplicationName()]; 297 #endif 298 299 /* Hand off to main application code */ 300 gCalledAppMainline = TRUE; 301 status = SDL_main (gArgc, gArgv); 302 303 /* We're done, thank you for playing */ 304 exit(status); 305 } 306 @end 307 308 309 @implementation NSString (ReplaceSubString) 310 311 - (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString 312 { 313 unsigned int bufferSize; 314 unsigned int selfLen = [self length]; 315 unsigned int aStringLen = [aString length]; 316 unichar *buffer; 317 NSRange localRange; 318 NSString *result; 319 320 bufferSize = selfLen + aStringLen - aRange.length; 321 buffer = (unichar *)NSAllocateMemoryPages(bufferSize*sizeof(unichar)); 322 323 /* Get first part into buffer */ 324 localRange.location = 0; 325 localRange.length = aRange.location; 326 [self getCharacters:buffer range:localRange]; 327 328 /* Get middle part into buffer */ 329 localRange.location = 0; 330 localRange.length = aStringLen; 331 [aString getCharacters:(buffer+aRange.location) range:localRange]; 332 333 /* Get last part into buffer */ 334 localRange.location = aRange.location + aRange.length; 335 localRange.length = selfLen - localRange.location; 336 [self getCharacters:(buffer+aRange.location+aStringLen) range:localRange]; 337 338 /* Build output string */ 339 result = [NSString stringWithCharacters:buffer length:bufferSize]; 340 341 NSDeallocateMemoryPages(buffer, bufferSize); 342 343 return result; 344 } 345 346 @end 347 348 349 350 #ifdef main 351 # undef main 352 #endif 353 354 355 /* Main entry point to executable - should *not* be SDL_main! */ 356 int main (int argc, char **argv) 357 { 358 /* Copy the arguments into a global variable */ 359 /* This is passed if we are launched by double-clicking */ 360 if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) { 361 gArgv = (char **) SDL_malloc(sizeof (char *) * 2); 362 gArgv[0] = argv[0]; 363 gArgv[1] = NULL; 364 gArgc = 1; 365 gFinderLaunch = YES; 366 } else { 367 int i; 368 gArgc = argc; 369 gArgv = (char **) SDL_malloc(sizeof (char *) * (argc+1)); 370 for (i = 0; i <= argc; i++) 371 gArgv[i] = argv[i]; 372 gFinderLaunch = NO; 373 } 374 375 #if SDL_USE_NIB_FILE 376 [SDLApplication poseAsClass:[NSApplication class]]; 377 NSApplicationMain (argc, argv); 378 #else 379 CustomApplicationMain (argc, argv); 380 #endif 381 return 0; 382 } 383 384